package project;

import java.util.Enumeration;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import oracle.kv.DurabilityException;
import oracle.kv.FaultException;
import oracle.kv.KVStore;
import oracle.kv.Key;
import oracle.kv.Operation;
import oracle.kv.OperationExecutionException;
import oracle.kv.ReturnValueVersion;
import oracle.kv.Value;
import oracle.kv.ValueVersion;
import oracle.kv.Version;
import oracle.kv.stats.KVStats;
import oracle.kv.stats.OperationMetrics;

public class Balancing {
	// numbre de item dans chaque store
	public static final int storeSize = Init_balance.storeSize;
	// nombre de store
	public static final int N = 1000 / storeSize;
	// matrix de store
	private Store[] stores;
	// threshold
	public static long thresholdValue = 10000;

	public Balancing(Store[] stores) {
		this.stores = stores;
	}

	/**
	 * Obtenir tous les operations sur un store
	 * 
	 * @param indexStore
	 *            l'index de store
	 * @param clear
	 *            si oui, eliminer les statistiques
	 * @return un metrix de operations relatives.
	 * */
	public List<OperationMetrics> getOperations(int indexStore, boolean clear) {
		KVStats kvstats = stores[indexStore].getStore().getStats(clear);
		return kvstats.getOpMetrics();
	}

	/**
	 * Obtenir les statistiques
	 * 
	 * @param indexStore
	 *            l'index de store
	 * @param clear
	 *            si oui, eliminer les statistiques
	 * @return les statistiques
	 * */
	public long getStatistics(int indexStore, boolean clear) {
		long statistics = 0;
		List<OperationMetrics> operations = getOperations(indexStore, clear);
		// iterer les operations
		for (OperationMetrics om : operations) {
			// si certain donnees sont deplacees, les supprimer dans
			// statistiques
			if (om.getOperationName().equalsIgnoreCase("deleteIfVersion")
					|| om.getOperationName().equalsIgnoreCase("delete"))
				statistics -= om.getTotalOps();
			else
				statistics += om.getTotalOps();
		}
		return statistics;
	}

	/**
	 * obtenir la cle de produit qui a la plus age version.
	 * 
	 * @param indexStore
	 *            l'index de store
	 * @return la cle de produit
	 * */
	public int getOlderVersion(int indexStore) {
		Enumeration<Integer> v = stores[indexStore].getVersions().keys();
		int maxVersionValue = 0;
		int maxVersionKey = 0;
		// iterer les versions dans kvstore
		while (v.hasMoreElements()) {
			Integer key = (Integer) v.nextElement();
			if (stores[indexStore].getVersions().get(key) >= maxVersionValue)
				maxVersionKey = key;
		}
		// obtenir la version maximum
		return maxVersionKey;
	}

	/**
	 * verifier si un store est trop charge
	 * 
	 * @param indexStore
	 *            l'index de store
	 * @return si trop charge, oui
	 * */
	public boolean isOverload(int indexStore) {
		boolean isOverload = false;
		if (getStatistics(indexStore, false) > thresholdValue) {
			isOverload = true;
		}
		return isOverload;
	}

	/**
	 * obtenir l'index de store qui est le moin charge
	 * 
	 * @param indexStore
	 *            l'index de store
	 * @param statistics
	 *            le statistique de store qui est trop charge
	 * @return index de store
	 * */
	public int getOverloadMin(int indexStore, long statistics) {
		int N = stores.length;
		long minOverload = statistics;
		int minStore = -1;
		// calcule le charge minimum
		for (int i = 0; i < N; i++) {
			if (indexStore != i) {
				long temp = getStatistics(i, false);
				if (minOverload > temp) {
					minOverload = temp;
					minStore = i;
				}
			}
		}
		return minStore;
	}

	/**
	 * Equillibrage de charge.
	 * 
	 * @param indexStore
	 *            l'index de store trop charge
	 * @param minIndexStore
	 *            l'index de store le moin charge
	 * @param keyProduit
	 *            le cle de produit qui dois etre deplace
	 * */
	public void balance(int indexStore, int minIndexStore, int keyProduit) {
		// verifier si la donnee deplacee est supprime ou pas
		boolean isDeleteSuccess = false;
		while (!isDeleteSuccess) {
			Key key = Key.createKey("P" + keyProduit, "quality");
			ValueVersion valueVersion = stores[indexStore].getStore().get(key);
			Version version = valueVersion.getVersion();
			// verifier si la donnee est bien deplacee dans le nouveau store ou pas
			boolean isAddSucccess = false;
			Value value = valueVersion.getValue();

			ArrayList<Operation> newOperations = new ArrayList<Operation>();
			// creer l'opearation de transaction
			Operation opAdd = stores[minIndexStore]
					.getStore()
					.getOperationFactory()
					.createPutIfVersion(key, value, version,
							ReturnValueVersion.Choice.NONE, true);
			newOperations.add(opAdd);
			try {
				// execute la transaction
				stores[minIndexStore].getStore().execute(newOperations);
				isAddSucccess = true;
			} catch (DurabilityException e1) {
				// roll back
				continue;
			} catch (OperationExecutionException e1) {
				// roll back
				continue;
			} catch (FaultException e1) {
				// roll back
				continue;
			}

			// l'index orgine de store obtenu par la fonction de hacharge
			int initStore = hachage(keyProduit);

			Key indexKey = Key.createKey("I" + keyProduit);
			// verifier si l'on reussit a changer le contenu de tableau de l'index ou pas.
			boolean isChangeSuccess = false;
			while (!isChangeSuccess) {
				ArrayList<Operation> operationIndexTable = new ArrayList<Operation>();
				// operation de transaction
				Operation opChange = stores[initStore]
						.getStore()
						.getOperationFactory()
						.createPut(
								indexKey,
								Value.createValue(new Integer(minIndexStore)
										.toString().getBytes()),
								ReturnValueVersion.Choice.NONE, true);
				operationIndexTable.add(opChange);
				try {
					// execute de transaction
					stores[initStore].getStore().execute(operationIndexTable);
					isChangeSuccess = true;
				} catch (DurabilityException e) {
					continue;
				} catch (OperationExecutionException e) {
					continue;
				} catch (FaultException e) {
					continue;
				}
			}
			ArrayList<Operation> operationsOld = new ArrayList<Operation>();
			// operation de transaction
			Operation opDelete = stores[indexStore]
					.getStore()
					.getOperationFactory()
					.createDeleteIfVersion(key, version,
							ReturnValueVersion.Choice.NONE, true);
			operationsOld.add(opDelete);
			try {
				// execute de la transaction
				stores[indexStore].getStore().execute(operationsOld);
				isDeleteSuccess = true;
			} catch (DurabilityException e) {
				// roll back
				continue;
			} catch (OperationExecutionException e) {
				// roll back
				continue;
			} catch (FaultException e) {
				// roll back
				continue;
			}

		}
	}

	/**
	 * Fonction de hachage. Determine Pi est stocker dans quel store.
	 * 
	 * @param i
	 *            index de produit
	 * @return index de store
	 * */
	private int hachage(int i) {
		int y = 0;
		if (i >= 0 && i <= 1000)
			y = (i - 1) / Init_balance.storeSize;
		else
			y = -1;
		return y;
	}
}
